﻿//-----------------------------------------------------------------------
// <copyright file="TransformState.cs" company="Nathan Miller">
// Copyright (c) Nathan Miller.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// </copyright>
//-----------------------------------------------------------------------
namespace TileTool.Base.Editor.Transform
{
    using System;
    using System.Collections.Generic;
    using TileTool.Common.Math;
    using TileTool.Editor.Grid;
    using TileTool.Editor.Transform;
    using TileTool.Message;
    using TileTool.Scene;
    using TileTool.Scene.Entity;
    using TileTool.Scene.Message;

    /// <summary>
    /// Transform state implementation.
    /// </summary>
    public class TransformState : ITransformState
    {
        #region Fields - Private

        /// <summary>
        /// Scene we are working with.
        /// </summary>
        private ISceneManager scene;

        /// <summary>
        /// Grid we are using.
        /// </summary>
        private IGrid grid;

        /// <summary>
        /// Snap state we are using.
        /// </summary>
        private ISnapState snap;

        /// <summary>
        /// Current coordinate system.
        /// </summary>
        private CoordinateSystem system;

        /// <summary>
        /// Current pick object.
        /// </summary>
        private IEntity pickObject;

        #endregion

        #region Constructor

        /// <summary>
        /// Transform state constructor.
        /// </summary>
        /// <param name="scene">Scene to use to track selection changes, etc.</param>
        public TransformState(ISceneManager scene)
        {
            if (scene == null)
            {
                throw new ArgumentNullException("scene", "Scene must be non-null.");
            }

            this.scene = scene;

            this.system = CoordinateSystem.World;

            this.UpdateMatrix();
        }

        #endregion

        #region Events

        /// <summary>
        /// Event triggered when the state changes.
        /// </summary>
        public event Action<ITransformState> Changed;

        #endregion

        #region Properties - Public

        /// <summary>
        /// Grid we are using.
        /// </summary>
        public IGrid Grid
        {
            get
            {
                return this.grid;
            }

            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("Grid must be non-null.");
                }

                this.grid = value;
            }
        }

        /// <summary>
        /// Gets the snap state we are using. Should always be non-null.
        /// </summary>
        public ISnapState SnapState
        {
            get
            {
                return this.snap;
            }

            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("Snap state must be non-null.");
                }

                this.snap = value;
            }
        }

        /// <summary>
        /// Gets the current coordinate system for the transform state.
        /// </summary>
        public CoordinateSystem System
        {
            get
            {
                return this.system;
            }

            set
            {
                this.system = value;
                this.UpdateMatrix();
            }
        }

        /// <summary>
        /// Gets the currently picked object which we will use as our coordinate system.
        /// 
        /// CoordinateSystem must be CoordinateSystem.Pick for this to be valid.
        /// </summary>
        public IEntity PickObject
        {
            get
            {
                return this.pickObject;
            }

            set
            {
                this.pickObject = value;
                this.UpdateMatrix();
            }
        }

        /// <summary>
        /// Gets the matrix for our coordinate system.
        /// </summary>
        public Matrix2D Matrix
        {
            get;
            private set;
        }

        #endregion

        #region Methods - Public

        /// <summary>
        /// Called to force the transform state to update itself.
        /// </summary>
        public void Update()
        {
            this.UpdateMatrix();
        }

        #endregion

        #region Methods - Private

        /// <summary>
        /// Called when the state changes.
        /// </summary>
        private void StateChanged()
        {
            Action<ITransformState> temp = this.Changed;
            if (temp != null)
            {
                temp(this);
            }
        }

        /// <summary>
        /// An event happened which has caused the current transform state to be stale,
        /// so update it.
        /// </summary>
        private void UpdateMatrix()
        {
            IEntityCollection selection = this.scene.Selection;
            int selectionSize = selection.Count;

            if (selectionSize == 0)
            {
                this.Matrix = Matrix2D.Identity;
            }
            else if (selectionSize != 1)
            {
                this.UpdateMatrixMultiSelection(selection);
            }
            else
            {
                this.UpdateMatrixSingleSelection(selection);
            }

            this.StateChanged();
        }

        /// <summary>
        /// Update the matrix when we only have a single object selected.
        /// </summary>
        /// <param name="selection">Current scene selection.</param>
        private void UpdateMatrixSingleSelection(IEntityCollection selection)
        {
            Matrix2D res = Matrix2D.Identity;
            IEntity entity = selection[0];

            switch (this.System)
            {
                case CoordinateSystem.Local:
                    res = entity.Matrix;
                    break;
                case CoordinateSystem.World:
                    res.Pos = entity.Pos;
                    break;
                case CoordinateSystem.Pick:
                    // res = this.PickObject.Matrix;
                    res = entity.Matrix;
                    break;
            }

            this.Matrix = res;
        }

        /// <summary>
        /// Update the matrix when we only have many objects selected.
        /// </summary>
        /// <param name="selection">Current scene selection.</param>
        private void UpdateMatrixMultiSelection(IEntityCollection selection)
        {
            Matrix2D res = Matrix2D.Identity;

            switch (this.System)
            {
                case CoordinateSystem.Local:
                case CoordinateSystem.World:
                    {
                        Rect2D bounds = EntityUtility.GetBoundingRect(selection);

                        res.Pos = bounds.Pos;
                    }

                    break;
                case CoordinateSystem.Pick:
                    {
                        Rect2D bounds = EntityUtility.GetBoundingRect(selection);

                        res.Pos = bounds.Pos;
                    }

                    break;
            }

            this.Matrix = res;
        }

        #endregion
    }
}
